a predictable state management library for Dart.
Simple & Lightweight
Highly Testable
For Dart, Flutter, and AngularDart
Flutter BLoc套件是由Felix Angelov所開發的套件,能夠幫助開發人員實作BLoC pattern。他將BLoC包裝成更容易理解和維護的框架,而且把許多細節都幫我們做好了(例如關閉Stream、Log等等)。
Felix Angelov將Bloc分成兩個套件:
BlocBuilder
、BlocProvider
、BlocListener
。今天先介紹Bloc套件內的名詞和定義Bloc物件。
enum
例如:enum LoginEvent{LoginWithGooglePressed, LoginWithCredentialsPressed}
// LoginWithGooglePressed = 0;
// LoginWithCredentialsPressed = 1;
或者使用class
來定義,使用class
的好處是可以另外傳遞資訊到Bloc中,例如需要傳遞信箱和密碼的時候就要使用class
來定義事件。
class LoginWithGooglePressed extends LoginEvent {
@override
String toString() => 'LoginWithGooglePressed';
}
class LoginWithCredentialsPressed extends LoginEvent {
final String email;
final String password;
LoginWithCredentialsPressed({@required this.email, @required this.password})
: super([email, password]);
@override
String toString() {
return 'LoginWithCredentialsPressed { email: $email, password: $password }';
}
}
factory LoginState.init() {
return LoginState(
isSuccess: false,
isFailure: false,
);
}
factory LoginState.failure() {
return LoginState(
isSuccess: false,
isFailure: true,
);
}
factory LoginState.success() {
return LoginState(
isSuccess: true,
isFailure: false,
);
}
例如當登入事件完成後可以在log看到以下資訊。(需要override onTransition或使用BlocDelegate)
Transition {
currentState: LoginState {
isSuccess: false,
isFailure: false,
},
event: LoginWithGooglePressed,
nextState: LoginState {
isSuccess: true,
isFailure: false,
}
}
定義Bloc物件需先繼承套件提供的Bloc class,並給予定義好的Event物件和State物件。
import 'package:bloc/bloc.dart';
// LoginBloc會接收LoginEvent作為輸入,並輸出LoginState。
class LoginBloc extends Bloc<LoginEvent, LoginState> {
}
還需要給予Bloc初始的State,在還沒接收到任何Event前就使用初始的State,不然UI會不知道該顯示什麼畫面才好。
// 這裡的LoginState.init()和上面State程式碼的一樣。
@override
LoginState get initialState => LoginState.init();
另外一定要實作的是mapEventToState
,將接收到的Event用對應的商業邏輯(Business Logic)做處理,最後回傳包裝成Stream的State。
@override
Stream<LoginState> mapEventToState(LoginEvent event) async* {
// 依照接收到的Event執行對應的方法
if (event is LoginWithGooglePressed) {
yield* _mapLoginWithGooglePressedToState();
} else if (event is LoginWithCredentialsPressed) {
yield* _mapLoginWithCredentialsPressedToState(
email: event.email,
password: event.password,
);
}
}
Stream<LoginState> _mapLoginWithGooglePressedToState() async* {
// 使用Google帳號進行登入,成功就回傳sucess的State
// 失敗則回傳Failure的State
try {
await _userRepository.signInWithGoogle();
yield LoginState.success();
} catch (_) {
yield LoginState.failure();
}
}
Stream<LoginState> _mapLoginWithCredentialsPressedToState({
String email,
String password,
}) async* {
// 使用帳號和密碼進行登入,成功就回傳sucess的State
// 失敗則回傳Failure的State
try {
await _userRepository.signInWithCredentials(email, password);
yield LoginState.success();
} catch (_) {
yield LoginState.failure();
}
}
如此一來就定義好LoginBloc所需的所有東西了,當使用者做了某些行為會觸發Bloc的商業邏輯,之後Bloc會更新State,UI就依照State來更新介面。
那麼該如何觸發Event呢?
Bloc內提供dispatch
方法可以用Event作為參數,它會觸發mapEventToState
,接著就是執行Event與State的轉換。
// 示意用程式碼
void main() {
LoginBloc bloc = LoginBloc();
// 略...
RaisedButton(
onPressed: (){
bloc.dispatch(LoginWithGooglePressed);
},
child: Text('Google Login'),
)
}
在Transitions章節有提到,如果要log或顯示Transition就需要override onTransition
(另外還有onError
和onEvent
分別能處理Exception和Event資訊),但一個專案中可能會有多個Bloc Class,如果每個都要override實在很麻煩。
這時候就可以使用BlocDelegate,它可以一次設定所有的Bloc class。
定義的方式如下,可以依照需求添加功能,例如例外處理:
class SimpleBlocDelegate extends BlocDelegate {
@override
void onEvent(Bloc bloc, Object event) {
super.onEvent(bloc, event);
print(event);
}
@override
void onTransition(Bloc bloc, Transition transition) {
super.onTransition(bloc, transition);
print(transition);
}
@override
void onError(Bloc bloc, Object error, StackTrace stacktrace) {
super.onError(bloc, error, stacktrace);
print('$error, $stacktrace');
}
}
使用起來也很簡單只需要在main()裡加上一行程式碼就可以了,剩下就交給Delegate處理:
main() {
BlocSupervisor.delegate = SimpleBlocDelegate();
runApp(App());
}
今天介紹了「bloc」套件提供的物件以及使用方式,第一次接觸可能會覺得很複雜又得要定義State和Event很麻煩。不過就是因為這個套件將BLoC的Input和Output定義得很清楚,後續要做測試和追蹤會變得非常容易。
另外有方便的擴充套件只要輸入bloc名稱就可以產生基本的bloc模板,所以需要打的程式碼其實並不多。
明天來介紹「flutter_bloc」套件提供的各種flutter widget的用法以及擴充套件該如何安裝跟使用。